#pragma once
#include "../Comm/comm.hpp"
#include "../Comm/util.hpp"
#include "../Comm/log.hpp"
// #include "oj_model.hpp"
#include "oj_model_mysql.hpp"
#include "oj_view.hpp"
#include "jsoncpp/json/json.h"
#include "../cpp-httplib/httplib.h"

namespace ns_control
{
    using namespace ns_util;
    using namespace ns_log;
    using namespace ns_model;
    using namespace ns_view;

    // 提供服务的主机
    class Machine
    {
    public:
        std::string _ip;  // 主机的ip
        int _port;        // 主机的port
        uint64_t _load;   // 主机的负载
        std::mutex *_mtx; // 为了确保原子性,需要进行加锁,C++中的mutex不允许拷贝,因此使用指针的方式进行传参

    public:
        Machine()
            : _ip(""), _port(0), _load(0), _mtx(nullptr)
        {
        }
        ~Machine() {}

    public:
        void IncLoad()
        {
            if (_mtx)
                _mtx->lock();
            ++_load;
            if (_mtx)
                _mtx->unlock();
        }

        void DecLoad()
        {
            if (_mtx)
                _mtx->lock();
            --_load;
            if (_mtx)
                _mtx->unlock();
        }

        uint64_t Load()
        {
            uint64_t load;
            if (_mtx)
                _mtx->lock();
            load = _load;
            if (_mtx)
                _mtx->unlock();

            return load;
        }
    };

    const std::string service_machine = "./conf/service_machine.conf";
    // 负载均衡
    class LoadBlance
    {
    private:
        // 提供编译服务的所有主机
        std::vector<Machine> _machines;
        // 在线的主机列表
        std::vector<int> _online;
        // 离线的主机列表
        std::vector<int> _offline;
        // 保证LoadBlance的数据安全
        std::mutex _mtx;

    public:
        LoadBlance()
        {
            assert(LoadConf(service_machine));
        }
        ~LoadBlance() {}

    public:
        bool LoadConf(const std::string &machine_conf)
        {
            std::ifstream in(machine_conf);
            if (!in.is_open())
            {
                LOG(ERROR) << "加载 " << machine_conf << " 主机失败" << std::endl;
                return false;
            }
            std::string line;
            while (getline(in, line))
            {
                std::vector<std::string> token;
                StringUtil::SplitString(line, &token, ":");
                if (token.size() != 2)
                {
                    LOG(ERROR) << "切分" << line << "失败" << std::endl;
                    continue;
                }
                Machine m;
                m._ip = token[0];
                m._port = atoi(token[1].c_str());
                m._load = 0;
                m._mtx = new std::mutex;

                _online.push_back(_machines.size());
                _machines.push_back(m);
            }
            LOG(INFO) << "加载 " << machine_conf << " 主机成功" << std::endl;
            in.close();
            return true;
        }

        // id 输出型参数
        // *m 输出型参数
        bool SmartChoice(int *id, Machine **m)
        {
            // 找到最小负载的主机
            _mtx.lock();
            int online_number = _online.size();
            if (online_number == 0)
            {
                _mtx.unlock();
                LOG(FATAL) << "所有主机都已经掉线, 请检查..." << std::endl;
                return false;
            }

            // 通过遍历的方式找到负载最小的主机
            uint64_t min_load = _machines[_online[0]].Load();
            (*id) = _online[0];
            (*m) = &_machines[_online[0]];
            for (int i = 1; i < online_number; ++i)
            {
                uint64_t curr_load = _machines[_online[i]].Load();
                if (min_load > curr_load)
                {
                    min_load = curr_load;
                    (*id) = _online[i];
                    (*m) = &_machines[_online[i]];
                }
            }

            _mtx.unlock();
            return true;
        }

        void OfflineMachine(int which)
        {
            _mtx.lock();
            // 要关闭某台主机,首先在在线列表中查询是否有这台主机
            for (auto iter = _online.begin(); iter != _online.end(); ++iter)
            {
                // 如果有, 将他从_online中移除, 同时添加到_offline中
                if (*iter == which)
                {
                    _machines[which]._load = 0;
                    _offline.push_back(*iter);
                    _online.erase(iter);
                    break;
                }
            }
            _mtx.unlock();
        }

        void OnlineMachine()
        {
            _mtx.lock();
            for (auto iter = _offline.begin(); iter != _offline.end();)
            {
                _online.push_back(*iter);
                iter = _offline.erase(iter);
            }
            _mtx.unlock();

            LOG(INFO) << "所有的主机又上线了" << std::endl;
        }

        void ShowMachines()
        {
            _mtx.lock();
            std::cout << "当前在线主机列表: ";
            for (auto &id : _online)
            {
                std::cout << id << " ";
            }
            std::cout << std::endl;

            std::cout << "当前离线主机列表: ";
            for (auto &id : _offline)
            {
                std::cout << id << " ";
            }
            std::cout << std::endl;
            _mtx.unlock();
        }
    };

    // 业务逻辑控制器
    class Control
    {
    private:
        Model _model;
        View _view;
        LoadBlance _loadblance;

    public:
        Control() {}
        ~Control() {}

    public:
        void RecoveryMachine()
        {
            _loadblance.OnlineMachine();
            _loadblance.ShowMachines();
        }

        bool AllQuestions(std::string *html)
        {
            std::vector<Question> token;
            if (_model.GetAllQuestions(&token))
            {
                sort(token.begin(), token.end(), [](const Question &q1, const Question &q2)
                     { return atoi(q1._number.c_str()) < atoi(q2._number.c_str()); });
                // 将获取到的所有题目通过渲染
                _view.AllExpendHtml(token, html);
            }
            else
            {
                *html = "获取题目失败, 形成题目列表失败";
                return false;
            }
            return true;
        }

        bool OneQuestion(const std::string &number, std::string *html)
        {
            Question q;
            if (_model.GetOneQuestion(number, &q))
            {
                // 将获取到序号为number的题目通过渲染
                _view.OneExpendHtml(q, html);
            }
            else
            {
                *html = "获取题目:" + number + "失败";
                return false;
            }
            return true;
        }

        // in_json: code - input
        void Judge(const std::string &number, const std::string &in_json, std::string *out_json)
        {
            // LOG(DEBAG) << "in_json" << in_json << "\nnumber = " << number << std::endl;

            // 1. 根据题号获取题目信息, 拿到题目的细节
            Question q;
            _model.GetOneQuestion(number, &q);

            // 2. 反序列化in_json, 得到code和input
            Json::Value in_value;
            Json::Reader reader;
            reader.parse(in_json, in_value);
            std::string code = in_value["code"].asString();
            std::string input = in_value["input"].asCString();

            // 3. 构建编译运行的代码, code + tial.cpp
            std::string compile_code = code + "\n" + q._tail;
            Json::Value compile_value;
            compile_value["code"] = compile_code;
            compile_value["input"] = input;
            compile_value["cpu_limit"] = q._cpu_limit;
            compile_value["mem_limit"] = q._mem_limit;
            Json::StyledWriter writer;
            std::string compile_json = writer.write(compile_value);

            // 4. 负载均衡的选择服务器, 选择负载最低的主机
            // 一直轮询选择主机, 直到选到可以使用的主机, 否则就是全部主机都挂掉
            while (true)
            {
                int id = 0;
                Machine *m = nullptr;
                if (!_loadblance.SmartChoice(&id, &m))
                {
                    break;
                }
                LOG(INFO) << "选择主机成功, 主机id: " << id << " 详情: " << m->_ip << " : " << m->_port
                          << " 当前主机的负载是 " << m->Load() << std::endl;
                // 5. 通过http请求服务器, 进行编译运行请求
                httplib::Client cli(m->_ip, m->_port);
                m->IncLoad();
                if (auto res = cli.Post("/compiler_runner", compile_json, "application/json; charset=utf-8"))
                {
                    // 6. 得到运行结果, 返回out_json
                    if (res->status == 200)
                    {
                        (*out_json) = res->body;
                        m->DecLoad();
                        LOG(INFO) << "请求编译运行服务成功..." << std::endl;
                        break;
                    }
                    m->DecLoad();
                }
                else
                {
                    LOG(INFO) << "当前请求的主机id: " << id << "详情: " << m->_ip << ":" << m->_port << " 可能已经掉线" << std::endl;
                    _loadblance.OfflineMachine(id);
                    _loadblance.ShowMachines();
                }
            }
        }
    };
}